# -*- coding: utf-8 -*-
# @Project : ConCox-auto-test-model
# @File    : Package0x8d.py
# @Author  : 朱宽
# @Time    : 2021/7/14 15:35
# @Software: Win10 / Python3 / Pycharm
from config.globalConfig import test_data_path
from src.utils import Crc
from array import array


class Package0x8d():
    '''
    这个类主要实现0x8d协议，将音频文件分片然后按照0x8d协议分片组包
    '''
    def __init__(self,fileName):
        self.filePath=test_data_path+fileName

    def shard_packets(self):
        '''
        这个方法是这个类的主方法
        :return: 以列表形式按顺序，返回所有的分片包。['分片1','',...,'分片n']
        '''
        byte_array = array('B') #定义一个字节数组
        fb = open(self.filePath, 'rb') #按二进制读取音频文件
        byte_array.frombytes(fb.read()) #将读取的二进制数据转换为字节数组；转换之后，byte_array变量中就存储了以字节数组形式存储的文件内容
        fb.close() #关闭文件
        byte_array_lists=list(byte_array) #将字节数组列表化，方便后续依据0x8d协议构建每一个音频文件的分片数据包；在代码中完全构建
        byte_array_lists_sharding=self.sharding(byte_array_lists) #调用类内方法对音频内容列表进行分片，长度是3072
        send_data_lists=[]
        for i in range(0,len(byte_array_lists_sharding)):
            tmp=self.combined_packet(n=i+1,data_lists=byte_array_lists_sharding[i],byte_array_lists=byte_array_lists)
            send_data_lists.append(tmp)

        return send_data_lists

    def sharding(self,byte_array_lists):
        '''
        这个方法主要是实现
        :param byte_array_lists:[]，以字节列表的形式存储音频文件
        :return:返回分段后的列表；[[分片1],[分片2],...,[分片n]]
        '''
        result=[] #用于存储切片后的每个分片
        for i in range(0,len(byte_array_lists),3072): #i在 [0,len())中循环，步长为3072，即下一个值是上一个值加3072
            if len(byte_array_lists[i:])<=3072: #当当前位置至最后一个位置的长度小于等于3072时，直接将剩余的元素以列表形式添加进列表中
                result.append(byte_array_lists[i:])
            else:
                result.append(byte_array_lists[i:i+3072])

        return result

    def combined_packet(self,n,data_lists,byte_array_lists):
        '''
        对每一个传入的音频内容，组合成符合0x8d协议的16进制字符串的数据
        :param n: 传入第几个分片；从1开始
        :param data_lists:字节列表形式
        :param byte_array_lists:文件总内容
        :return:
        '''
        crc_class=Crc()# 初始化

        #内容
        tmp=data_lists #将传入的数据列表暂存在tmp变量中；即0x8d中的 内容 字段

        #标识位
        identify='11091602381A' # 0x8d中的 标识位 字段，此位置为六个字节，传输监听开始的日期时间
        identify_lists=self.handle_data(6,identify)#调用方法，将整数型数据转换为字节列表
        tmp=tmp+identify_lists # 内容+标识位；字节列表形式

        #信息序列号
        serial_lists=self.handle_data(2,n,data_type='int') #将整数转换成字节列表
        tmp=tmp+serial_lists # 内容+标识位+信息序列号

        #当前内容长度
        current_content_length=len(data_lists) # 0x8d中的 当前内容长度 字段
        cur_con_len_lists=self.handle_data(2,current_content_length,data_type='int') #调用方法，将整数型数据转换为字节列表
        tmp=cur_con_len_lists+tmp # 当前内容长度+内容+标识位+信息序列号；字节列表形式

        #开始位置
        start=(n-1)*3072
        start_lists=self.handle_data(4,start,data_type='int')
        tmp=start_lists+tmp # 开始位置+当前内容长度+内容+标识位+信息序列号；字节列表形式

        #文件错误校验，CRC校验，2个字节
        crc_16_all_file=crc_class.crc16(pData=data_lists) # 2个字节，16进制字符串
        crc_16_all_file_lists=self.handle_data(2,crc_16_all_file)
        tmp=crc_16_all_file_lists+tmp # 文件错误校验+开始位置+当前内容长度+内容+标识位+信息序列号；字节列表形式

        #文件错误校验类型
        crc_type_lists=self.handle_data(1,'00')
        tmp=crc_type_lists+tmp # 文件错误校验类型+文件错误校验+开始位置+当前内容长度+内容+标识位+信息序列号；字节列表形式

        #文件总长度
        file_total_length=len(byte_array_lists)
        file_total_length_lists=self.handle_data(4,file_total_length,data_type='int')
        tmp=file_total_length_lists+tmp # 文件总长度+文件错误校验类型+文件错误校验+开始位置+当前内容长度+内容+标识位+信息序列号；字节列表形式

        #文件类型
        file_type_lists=self.handle_data(1,'00')
        tmp=file_type_lists+tmp # 文件类型+文件总长度+文件错误校验类型+文件错误校验+开始位置+当前内容长度+内容+标识位+信息序列号；字节列表形式

        #协议号
        protocol_number_lists=self.handle_data(1,'8d')
        tmp=protocol_number_lists+tmp # 协议号+文件类型+文件总长度+文件错误校验类型+文件错误校验+开始位置+当前内容长度+内容+标识位+信息序列号；字节列表形式

        #包长度
        package_length=len(tmp)+2
        package_length_lists=self.handle_data(2,package_length,data_type='int')
        tmp=package_length_lists+tmp # 包长度+协议号+文件类型+文件总长度+文件错误校验类型+文件错误校验+开始位置+当前内容长度+内容+标识位+信息序列号；字节列表形式

        ''' 
        至此，tmp中存储了 包长度--信息序列号 的内容；（字节列表形式）
        用处有2处：
            1、用于计算错误校验位，tmp就是字节列表形式，直接调用crc算法传入即可
            2、将这部分字节列表转换为16进制字符串，然后拼接 起始位、错误校验、停止位，构成一个完整的数据包
        '''
        crc_16=crc_class.crc16(pData=tmp) # 错误校验

        package_end='7979'+bytearray(tmp).hex()+crc_16+'0d0a'

        return package_end.lower()

    def handle_data(self,n,data,data_type='hex_str'):
        '''
        这个方法主要实现将某数据转换为字节列表
        :param n: 几个字节长度，2个16进制数是一个字节
        :param data: 传入的数据
        :param type: 数据类型，默认为16进制字符串型
        :return:返回字节列表形式
        '''
        try:
            if data_type == 'int':
                data=hex(data)[2:].zfill(2*n) #将整数型数据，转换为指定位数的16进制字符串

            y = bytearray.fromhex(data)  # var1 = bytearray.fromhex("01020304")  #将hex string 转化为 字节数组。
            result = list(y)  # 将字节数组列表化
            return result
        except Exception as e:
            print(e)

if __name__=='__main__':
    import json

    test=Package0x8d(fileName='test_0x8d.m4a')
    send_data_lists=test.shard_packets()
    print('长度为：',len(send_data_lists))
    print('内容为：\n',json.dumps(send_data_lists,indent=4,ensure_ascii=False))